package zheezes.eisp.autotask.model;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.List;
import java.util.Observable;
import java.util.Observer;
import java.util.Properties;
import java.util.concurrent.ConcurrentLinkedQueue;

import org.apache.log4j.Logger;
import org.apache.log4j.PropertyConfigurator;
import org.springframework.context.support.AbstractApplicationContext;

import zheezes.eisp.data.dao.DataAccessor;
import zheezes.eisp.data.orm.CalcTask;
import zheezes.eisp.data.orm.CollectTask;
import zheezes.eisp.util.SpringUtil;
import zheezes.util.DateFormatUtil;
import zheezes.util.DateUtil;
import zheezes.util.Log4jUtil;
import zheezes.util.Message;
import zheezes.util.ProcLock;
import zheezes.util.Util;


public class AutotaskManager extends Observable implements Runnable, Observer {
	private static final String DEFAULT_AUTOTASK_FILE = "autotask.ini";
	
	public static final int START_CREATE = 0;
	public static final int CREATE_SUCCESS = 1;
	public static final int CREATE_FAILURE = -1;

	private static final Logger logger = Logger.getLogger(AutotaskManager.class);
	
	private File file;
	protected Calendar schedule = Calendar.getInstance();
	
	private static final ProcLock lock = new ProcLock(new File(System.getProperty("java.io.tmpdir"), "eisp_autotask.lock"));

	protected DataAccessor dataAccessor;
	
	private volatile boolean terminated = false;
	private int interrupt = 1000;
	
	// protected Map<Integer, AbstractAutoTaskCreator> tmap = new ConcurrentHashMap<Integer, AbstractAutoTaskCreator>();
	protected Collection<AbstractAutoTaskCreator> cmap = new ConcurrentLinkedQueue<AbstractAutoTaskCreator>();
	
	public AutotaskManager() {
		// to be sure every type task has only one creator
		/*
		tmap.put(AbstractAutoTaskCreator.RES_METER_DAILY_FROZEN_COLLECT_TASK, new ResMeterDailyFrozenCollectTaskCreator());
		tmap.put(AbstractAutoTaskCreator.VIP_METER_HOUR_FROZEN_COLLECT_TASK, new VipMeterHourFrozenCollectTaskCreator());
		tmap.put(AbstractAutoTaskCreator.SUMMATOR_DAILY_FROZEN_COLLECT_TASK, new SummatorDailyFrozenCollecTaskCreator());
		tmap.put(AbstractAutoTaskCreator.SUMMATOR_INSTANT_VALUE_COLLECT_TASK, new SummatorInstantValueCollectTaskCreator());
		tmap.put(AbstractAutoTaskCreator.GROUP_STAT_CALC_TASK, new GroupStatCalcTaskCreator());
		*/
	}
	
	public static void main(String[] args) {
		PropertyConfigurator.configure("log4j.properties");
		Log4jUtil.setLogFile("autotask.log");
		final Thread mth = Thread.currentThread();
		AbstractApplicationContext context = SpringUtil.getApplicationContext();
		final AutotaskManager manager = (AutotaskManager) context.getBean("autotaskManager");
		Runtime.getRuntime().addShutdownHook(new Thread() {
			public void run() {
				logger.info("Process shutdown");
				manager.stop();
				try {
					mth.join(5000);
				} catch (InterruptedException e) {
					logger.error(e.getMessage(), e);
				}
			}
		});
		manager.run();
		// in the destroy function, only one code, close();
		context.destroy();
	}

	/* When you create a task twice, there is nothing serious,
	 * because the task handler know 
	 * if the task should to execute all or part or not 
	 */
	@SuppressWarnings({ "rawtypes", "unchecked" })
	public int createTask() {
		List list = new ArrayList();
		dataAccessor.beginTransaction();
		for (AbstractAutoTaskCreator creator : /*tmap.values()*/ cmap) {
			if (creator.enable()) {
				creator.setDataAccessor(dataAccessor);
				creator.setShedule(schedule);
				List tl = creator.mktask();
				if (tl != null) {
					list.addAll(tl);
				} else {
					dataAccessor.rollback();
					return -1;
				}
				tl.clear();
			}
		}
		for (Object o : list) {
			int rt = 0;
			if (o instanceof CollectTask) {
				CollectTask task = (CollectTask) o;
				rt = dataAccessor.replaceCollectTask(task);
			} else if (o instanceof CalcTask) {
				CalcTask task = (CalcTask) o;
				rt = dataAccessor.replaceCalcTask(task);
			} else {
				logger.warn("undefined task type");
			}
			if (rt != 0) {
				dataAccessor.rollback();
				return -1;
			}
		}
		list.clear();
		if (dataAccessor.commit() == 0) {
			schedule.add(Calendar.DATE, 1);
			if (save() == 0) {
				return 0;
			}
		}

		return -1;
	}

	public void run() {
		lock.addObserver(this);
		if(lock.lock() == 0) {
			if((file = searchConfig()) != null) {
				while(!terminated) {
					if (load() == 0) { 
						if (DateUtil.compare(schedule, Calendar.getInstance(), Calendar.DATE) < 0) {
							Message msg = null;
							if(createTask() == 0) {
								msg = new Message(CREATE_SUCCESS, schedule);
							} else {
								msg = new Message(CREATE_FAILURE, schedule);
							}
							setChanged();
							notifyObservers(msg);
						} else {
							Util.msleep(interrupt);
						}
						// asleep(BREAK);
					} else { // load config file failure, i don't know how to execute next
						setChanged();
						notifyObservers();
					}
				}
			}
			lock.free();
		} else {
			setChanged();
			notifyObservers();
		}
	}
	
	private void terminate() {
		terminated = true;
//		waken();		
	}

	public void stop() {
		logger.info("autotask manager stop");
		terminate();
		// lock.free();
	}

	private File searchConfig() {
		File file = null;
		if (!(file = new File(DEFAULT_AUTOTASK_FILE)).exists()) {
			return new File(DEFAULT_AUTOTASK_FILE);
		}
		return file;
	}

	private int load() {
		Properties props = new Properties();
		// if file not exist, i will create new
		if(file.exists() && file.canRead()) {
			InputStream in = null;
			try {
				in = new FileInputStream(file);
				props.load(in);
				String schd = props.getProperty("schedule");
				if(schd != null) {
					try {
						schedule.setTime(DateFormatUtil.getDateFmt().parse(schd));
						return 0;
					} catch (ParseException e) {
						logger.info(e.getMessage(), e);
						logger.error(String.format("schedule entry is bad format"));
					}
				}
			} catch (IOException e) {
				logger.info(e.getMessage(), e);
				logger.error(String.format("config file %s is bad format, delete it will create new auto", file.getName()));
			} finally {
				try {
					if(in != null) {
						in.close();
					}
				} catch (IOException e) {
				}
			}
		} else if(!file.exists()) {
			if(save() == 0) {
				return 0;
			} else {
				logger.error(String.format("config file %s not exist and create failure", file.getName()));
			}
		} else { // file can't read
			logger.error(String.format("config file %s can't read", file.getName()));	
		}
		return -1;
	}

	private int save() {
		Properties props = new Properties();
		if(schedule == null) {
			schedule = Calendar.getInstance();
		}
		props.put("schedule", DateFormatUtil.getDateFmt().format(schedule.getTime()));
		FileOutputStream out = null;
		try {
			out = new FileOutputStream(file);
			props.store(out, String.format("last modifid at %s", DateFormatUtil.getDttmFmt().format(Calendar.getInstance().getTime())));
			return 0;
		}  catch (IOException e) {
			logger.debug(e.getMessage(), e);
		} finally {
			try {
				if(out != null) {
					out.close();
				}
			} catch (IOException e) {
			}
		}
		return -1;
	}
	
	/**
	 * call by lock callback
	 */
	public void update(Observable o, Object arg) {
		if(o == lock) {
			// stop model
			terminate();
			// stop view
			setChanged();
			notifyObservers();
		}
	}

	public Calendar getSchedule() {
		return schedule;
	}

	public void setDataAccessor(DataAccessor dataAccessor) {
		this.dataAccessor = dataAccessor;
	}

	public void setCreators(AbstractAutoTaskCreator[] creators) {
		for (AbstractAutoTaskCreator creator : creators) {
			// tmap.put(creator.getType(), creator);
			cmap.add(creator);
		}
	}
}
